### A Drug/Disease to Pubmed Article Link Prediction Guide

This guide will show how to run link prediction to suggest a disease or drug link to a pubmed article.

### Project A Graph

Project a graph for the pubmed articles. Include drug, disease, gene_protein foundational nodes. Include relationships between nodes, including the `has_extraction` pubmed article relationship to the foundational graph nodes.

## TODO: consider adding `anatomy`, but first fix the `node_index` field in a few of the `anatomy` nodes.

    call gds.graph.project(
      'pubmed_graph',
      [
        'disease',
        'drug',
        'gene_protein',
        'pubmed_document'
      ],
      {
        has_extraction: {orientation: 'UNDIRECTED', type:'*'},
        disease_disease: {orientation: 'UNDIRECTED', type:'*'},
        disease_protein: {orientation: 'UNDIRECTED', type:'*'},
        drug_protein: {orientation: 'UNDIRECTED', type:'*'},
        drug_drug: {orientation: 'UNDIRECTED', type:'*'},
        drug_effect: {orientation: 'UNDIRECTED', type:'*'},
        protein_protein: {orientation: 'UNDIRECTED', type:'*'},
        indication: {orientation: 'UNDIRECTED', type:'*'},
        contraindication: {orientation: 'UNDIRECTED', type:'*'}
      },
      {
        nodeProperties: ['model2vec_embeddings', 'label_one_hot_encoding']
      }
    )
    yield graphName AS graph, nodeProjection, nodeCount AS nodes, relationshipCount AS rels
    return graph, nodeProjection, nodes, rels;

Project a pubmed graph

### Merge Graph and Sentence Embeddings

Create embeddings using the https://neo4j.com/docs/graph-data-science/current/machine-learning/node-embeddings/fastrp/[fast random projection(fastrp)] algorithm. The parameters used are an embedding dimension size, 256 is recommended for graphs with 10^5 nodes or more. We use dimension `512` to make room for the `model2vec_embeddings` created with sentence transfomers. The `iterationWeights` are tuned to learn from closest neighbors first and then some 2nd hop neighbors.

    call gds.fastRP.mutate(
      'pubmed_graph',
        {
          embeddingDimension: 512,
          mutateProperty: 'fastrp-embedding',
          iterationWeights: [1.0, .10],
          normalizationStrength: -0.5,
          randomSeed: 75,
          featureProperties: ['model2vec_embeddings'],
          propertyRatio: .50
        }
      )
      yield nodePropertiesWritten;

Merge graph and sentence transformer embeddings

### Setup Link Prediction Pipeline

The pipeline will attempt to build models using logistic regression, random forest, or multilayer perceptron. The model with the best results will be used. Notice the training data is split between train and testing.

    // define the link prediction pipeline
    call gds.beta.pipeline.linkPrediction.create('pubmed-pipeline-1');

    // define embedding as a feature, can add other node properties
    call gds.beta.pipeline.linkPrediction.addFeature('pubmed-pipeline-1', 'hadamard', {
      nodeProperties: [
        'fastrp-embedding',
        'model2vec_embeddings'
      ]
    }) yield featureSteps;

    // configure test, training, validation data splits
    call gds.beta.pipeline.linkPrediction.configureSplit('pubmed-pipeline-1', {
      testFraction: 0.17,
      trainFraction: 0.59,
      validationFolds: 3
    })
    yield splitConfig;

    // define model type, each model will be tried in the autotuning step
    call gds.beta.pipeline.linkPrediction.addLogisticRegression('pubmed-pipeline-1')
    yield parameterSpace;

    CALL gds.beta.pipeline.linkPrediction.addRandomForest('pubmed-pipeline-1', {numberOfDecisionTrees: 10 })
    yield parameterSpace;

    // note the alpha namespace
    call gds.alpha.pipeline.linkPrediction.addMLP('pubmed-pipeline-1',
    {hiddenLayerSizes: [4, 2], penalty: 0.5, patience: 2, classWeights: [0.55, 0.45], focusWeight: {range: [0.0, 0.1]}})
    yield parameterSpace;

Create link prediction pipeline

### Train Disease to Article Model

Train disease to article model

                //
    call gds.beta.pipeline.linkPrediction.train('pubmed_graph', {
      pipeline: 'pubmed-pipeline-1',
      modelName: 'pubmed-disease-model',
      metrics: ['AUCPR', 'OUT_OF_BAG_ERROR'],
      sourceNodeLabel: 'pubmed_document',
      targetRelationshipType: 'has_extraction',
      targetNodeLabel: 'disease',
      randomSeed: 75
    }) yield modelInfo, modelSelectionStats
    return
      modelInfo.bestParameters AS winningModel,
      modelInfo.metrics.AUCPR.train.avg AS avgTrainScore,
      modelInfo.metrics.AUCPR.outerTrain AS outerTrainScore,
      modelInfo.metrics.AUCPR.test AS testScore,
      [cand IN modelSelectionStats.modelCandidates | cand.metrics.AUCPR.validation.avg] AS validationScores;

train model

### Train Drug to Article Model

Train drug to article model

    //
    call gds.beta.pipeline.linkPrediction.train('pubmed_graph', {
      pipeline: 'pubmed-pipeline-1',
      modelName: 'pubmed-drug-model',
      metrics: ['AUCPR', 'OUT_OF_BAG_ERROR'],
      sourceNodeLabel: 'pubmed_document',
      targetRelationshipType: 'has_extraction',
      targetNodeLabel: 'drug',
      randomSeed: 75
    }) yield modelInfo, modelSelectionStats
    return
      modelInfo.bestParameters AS winningModel,
      modelInfo.metrics.AUCPR.train.avg AS avgTrainScore,
      modelInfo.metrics.AUCPR.outerTrain AS outerTrainScore,
      modelInfo.metrics.AUCPR.test AS testScore,
      [cand IN modelSelectionStats.modelCandidates | cand.metrics.AUCPR.validation.avg] AS validationScores;

train model

### Predict Disease to Article

Predict across all diseases to articles.

    call gds.beta.pipeline.linkPrediction.predict.stream('pubmed_graph', {
      modelName: 'pubmed-disease-model',
      topN: 1000,
      threshold: 0.5
    })
    yield node1, node2, probability
    return probability, gds.util.asNode(node1).node_name as disease, gds.util.asNode(node2).title as article, gds.util.asNode(node2).pmcid as pmcid
    order by probability desc, disease, article;

Predict disease to article

### Predict Drug to Article

Predict across all drugs to articles.

    call gds.beta.pipeline.linkPrediction.predict.stream('pubmed_graph', {
      modelName: 'pubmed-drug-model',
      topN: 200,
      threshold: 0.5
    })
    yield node1, node2, probability
    return probability, gds.util.asNode(node1).node_name AS drug, gds.util.asNode(node2).title AS article, gds.util.asNode(node2).pmcid as pmcid
    order by probability desc, drug, article;

Predict drug to article

### K Nearest Neighbors (KNN)

K Nearest Neighbors

    call gds.knn.write(
      'pubmed_graph',
      {
        nodeProperties: ['fastrp-embedding'],
        nodeLabels: ['pubmed_document'],
        topK: 8,
        sampleRate: 1.0,
        deltaThreshold: 0.001,
        similarityCutoff: .85,
        randomSeed: 75,
        concurrency: 1,
        writeProperty: 'fastrp_score',
        writeRelationshipType: 'similar_article'
      }
    )
    yield similarityDistribution
    return similarityDistribution.mean as meanSimilarity;

KNN

### Similar Articles

View similar articles

    MATCH p=(d1:pubmed_document)-[r:similar_article]-(d2:pubmed_document)
    where r.fastrp_score > .5
    RETURN p;

view similar articles

### Clean Up

Delete graph, models and pipeline.

    // delete article knn links
    MATCH (d1:pubmed_document)-[r:similar_article]-(d2:pubmed_document)
    DELETE r;

    // drop model
    call gds.model.drop('pubmed-disease-model')
        yield modelName, modelType, modelInfo, loaded, stored, published;

    call gds.model.drop('pubmed-drug-model')
        yield modelName, modelType, modelInfo, loaded, stored, published;

    // drop pipeline
    call gds.pipeline.drop('pubmed-pipeline-1');

    // drop graph
    call gds.graph.drop('pubmed_graph');

Clean up